﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace KBEngine.Common
{
    public class ServerApp
    {
        Socket _socket;
        MemoryStream _receivebuffer = new MemoryStream();
        Dictionary<ushort, Action<MemoryStream>> msgProcesser_ = new Dictionary<ushort, Action<MemoryStream>>();
        private string _fireEvent;
        public bool connected { get => _socket != null; }
        public void connect(string host, ushort port)
        {
            System.Diagnostics.Debug.Assert(_socket == null);
            _fireEvent = $"__{nameof(ServerApp)}_{nameof(__FireMsg)}_{Define.getObjAddres(this)}";
            Event.registerIn(_fireEvent, this, nameof(__FireMsg));
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Unspecified);
            _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
            _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 5 * 1024 * 1024);
            _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);
            //_socket.Blocking = false;
            EndPoint endPoint = new IPEndPoint(IPAddress.Parse(host), port);
            _socket.Bind(endPoint);
        }
        public void close()
        {
            if (_socket == null)
                return;
            _socket.Close();
            _socket = null;
            Event.deregisterIn(_fireEvent, this, nameof(__FireMsg));
        }

        public void registerMsg(ushort msgID, Action<MemoryStream> func)
        {
            System.Diagnostics.Debug.Assert(!msgProcesser_.TryGetValue(msgID, out _));
            msgProcesser_[msgID] = func;
        }
        public void deregisterMsg(ushort msgID)
        {
            msgProcesser_.Remove(msgID);
        }
        public void send(Bundle bundle)
        {
            bundle.sendTCP(_socket);
        }
        public int recv(ref byte[] datas)
        {
            if (datas == null)
                datas = new byte[4096];
            return _socket.Receive(datas);
        }
        public void processOne(int timeout = 100)
        {
            if (!_socket.Connected)
                return;

            _socket.ReceiveTimeout = timeout;
            ReceiveBytes(null);
        }

        public void __FireMsg(Action<MemoryStream> func, MemoryStream stream)
        {
            func(stream);
            stream.reclaimObject();
        }

        private void ReceiveBytes(object asyncState)
        {
            try
            {
                _socket.BeginReceive(_receivebuffer.data(), _receivebuffer.wpos, (int)_receivebuffer.space(), SocketFlags.None, ReceiveCallback, asyncState);
            }
            catch (SocketException)
            {
                return;
            }
        }
        private void ReceiveCallback(IAsyncResult ar)
        {
            int reCount = _socket.EndReceive(ar);
            if (reCount == 0)
            {
                close();
                return;
            }
            _receivebuffer.wpos += reCount;
            processMsg();
            ReceiveBytes(ar.AsyncState);
        }
        private void processMsg()
        {
            while (_receivebuffer.length() >= 4)
            {
                var msgID = _receivebuffer.readUint16();
                bool get = msgProcesser_.TryGetValue(msgID, out var func);
                System.Diagnostics.Debug.Assert(get, $"unknown message id {msgID}");
                
                var msgLength = _receivebuffer.readUint16();
                if (_receivebuffer.length() < msgLength)
                {
                    _receivebuffer.rpos -= 4;
                    var newBuffer = _receivebuffer.getbuffer();
                    Array.Copy(newBuffer, _receivebuffer.data(), newBuffer.Length);
                    _receivebuffer.rpos = 0;
                    _receivebuffer.wpos = newBuffer.Length;
                    return;
                }

                _receivebuffer.rpos += (int)msgLength;
                MemoryStream stream = MemoryStream.createObject();
                stream.append(_receivebuffer.data(), (uint)(_receivebuffer.rpos - msgLength), msgLength);
                Event.fireIn(_fireEvent, func, stream);
            }
            _receivebuffer.clear();
        }
    }
}
